﻿using System;


namespace BizElements.BusinessLayer
{
    #region Documentation.
    /// <summary>
    /// Manages conflicts between concurrent business transactions by allowing only one business transaction at a time to access data.
    /// </summary>
    /// <remarks><para>Implements <b>Pessimistic Offline Lock</b> scheme. Pessimistic Offline Lock prevents 
    /// conflicts by avoiding them altogether. It forces a business transaction to acquire a lock on a piece 
    /// of data before it starts to use it, so that, most of the time, once you begin a business transaction 
    /// you can be pretty sure you'll complete it without being bounced by concurrency control.</para>
    /// <para>When to Use It?</para>
    /// <para>Pessimistic Offline Lock is appropriate when the chance of conflict between concurrent sessions is high
    /// or when the cost of a conflict is too high regardless of its likelihood. Prefer <b>Optimistic Offline Lock</b>
    /// in all other scenarios. Optimistic Offline Lock prevents conflicts between concurrent business transactions 
    /// by detecting a conflict and rolling back the transaction. Conflict detection is implemented by validating
    /// that the object hasn't been changed by another transaction in the meantime.</para>
    /// <para>How to use it?</para>
    /// <para>Preferer <b>Coarse-Grained Lock</b> or <b>Object Root Lock</b> pattern if possible. 
    /// Avoid locking all "physical" rows accessed by a transaction.</para>
    /// <para>Pessimistic Offline Lock scheme:
    /// <list type="number">
    /// <item>Acquire lock.</item>
    /// <item>If not acquired then abort operation or enter read-only mode if this suites you.</item>
    /// <item>Load object.</item>
    /// <item>Process/edit object.</item>
    /// <item>Check that you still own the lock. If lock has expired and cannot reacquire it then abort operation.</item>
    /// <item>Check that the persisted/old object version hasn't changed, ie. ensure that the lock was honored by others. 
    /// You can use Optimistic Offline Lock scheme to detect changes or you can reload entire object and compare it to the locked version.</item>
    /// <item>Write changes.</item>
    /// <item>Release lock.</item>
    /// </list>
    /// </para></remarks>
    /// <seealso cref="LockUtil"/><seealso cref="LockManagerFactory"/><seealso cref="LockMode"/>
    #endregion
    public interface ILockManager
    {
        #region Methods.

        /// <summary>
        /// Tries to acquire a default lock for the object specified in the request.
        /// </summary>
        /// <param name="owner">Actor trying to lock the object.</param>
        /// <param name="objectType">Type of the object to lock. May be null if the current instance is only used to lock a single object type.</param>
        /// <param name="objectId">ID of the object to lock.</param>
        /// <returns><b>false</b> if another <see cref="IActor"/> already locked the object; <b>true</b> otherwise.</returns>
        bool AcquireLock(IActor owner, string objectType, object[] objectId);

        /// <summary>
        /// Tries to acquire a lock for the object specified in the request.
        /// </summary>
        /// <param name="owner">Actor trying to lock the object.</param>
        /// <param name="mode">Indicates how an object should be locked. Some managers may only support the <see cref="LockMode.Default"/> mode.</param>
        /// <param name="timeout">When will the lock expire unless prologned/reacquired. If <b>null</b> the default timeout is used.</param>
        /// <param name="objectType">Type of the object to lock. May be null if the current instance is only used to lock a single object type.</param>
        /// <param name="objectId">ID of the object to lock.</param>        
        /// <returns><b>false</b> if another <see cref="IActor"/> already locked the object; <b>true</b> otherwise.</returns>
        bool AcquireLock(IActor owner, LockMode mode, TimeSpan? timeout, string objectType, object[] objectId);

        /// <summary>
        /// Checks whether a user may read the object specified in the request. 
        /// </summary>
        /// <param name="reader">Actor trying to read the object.</param>
        /// <param name="objectType">Type of the object to check. May be null if the current instance is only used to lock a single object type.</param>
        /// <param name="objectId">ID of the object to check.</param>
        /// <returns><b>false</b> if another user has acquired an exclusive lock for the object; otherwise <b>true</b>.</returns>
        bool CanRead(IActor reader, string objectType, object[] objectId);

        /// <summary>
        /// Checks whether a user may modify the object specified in the request. 
        /// </summary>
        /// <param name="owner">Actor trying to modify the object.</param>
        /// <param name="objectType">Type of the object to check. May be null if the current instance is only used to lock a single object type.</param>
        /// <param name="objectId">ID of the object to check.</param>
        /// <returns><b>true</b> if the user owns a lock for the specified object; <b>false</b> otherwise.</returns>
        bool CanWrite(IActor owner, string objectType, object[] objectId);

        /// <summary>
        /// Forcefully cancels a lock placed on the specified object by any user.
        /// </summary>
        /// <param name="objectType">Type of the object to unlock. May be null if the current instance is only used to lock a single object type.</param>
        /// <param name="objectId">ID of the object to unlock.</param>
        /// <remarks>This method should only be available to system administrators.</remarks>
        void KillLock(string objectType, object[] objectId);

        /// <summary>
        /// Releases a lock that has previously been acquired by a user.
        /// </summary>
        /// <param name="owner">Lock owner.</param>
        /// <param name="objectType">Type of the object to unlock. May be null if the current instance is only used to lock a single object type.</param>
        /// <param name="objectId">ID of the object to unlock.</param>
        void ReleaseLock(IActor owner, string objectType, object[] objectId);

        #endregion
    }
}